+#!/usr/bin/perl
+use strict;
+use warnings;
+use File::Path qw(make_path remove_tree);
+use File::Spec;
+use Cwd qw(abs_path);
+
+# ============================================================
+# CONFIGURATION CONSTANTS - Edit these as needed
+# ============================================================
+
+# Repository base path - where all repositories will be created
+use constant REPO_BASE => '/path/to/your/repos';
+
+# Master branch user - who can push to master
+use constant MASTER_BRANCH_USER => 'YOUR-GIT-USERNAME-HERE';
+
+# ============================================================
+# Check if running as root
+# ============================================================
+
+if ($< != 0) {
+ die "Error: This script must be run as root (use sudo)\n";
+}
+
+# ============================================================
+# TEMPLATES
+# ============================================================
+
+my $README_TEMPLATE = <<'END_README';
+
+<h1>HELLO WORLD</h1>
+
+END_README
+
+my $CONFIG_JS_TEMPLATE = <<'END_JS';
+END_JS
+
+my $HOOK_UPDATE_TEMPLATE = <<'END_HOOK';
+#!/bin/bash
+
+# Branch protection hook
+# Controls who can push to master and who can push to the repository at all
+
+refname="$1"
+oldrev="$2"
+newrev="$3"
+
+# Get the user who is pushing (set by SSH authorized_keys)
+PUSHER="${USER}"
+
+# Define the protected branch
+PROTECTED_BRANCH="refs/heads/master"
+
+# Define allowed user for master branch
+ALLOWED_MASTER_USER="__MASTER_USER__"
+
+# Define allowed contributors (space-separated list)
+# Edit this list to add/remove contributors for THIS repository
+ALLOWED_CONTRIBUTORS="__MASTER_USER__"
+
+# Check if user is allowed to push to this repository at all
+if [[ ! " ${ALLOWED_CONTRIBUTORS} " =~ " ${PUSHER} " ]]; then
+ echo "==============================================="
+ echo "ERROR: You are not authorized to push to this repository."
+ echo "==============================================="
+ echo ""
+ echo "You are authenticated as: $PUSHER"
+ echo ""
+ echo "Allowed contributors: $ALLOWED_CONTRIBUTORS"
+ echo ""
+ echo "Please contact the repository owner for access."
+ echo "==============================================="
+ exit 1
+fi
+
+# Check if pushing to protected branch
+if [ "$refname" = "$PROTECTED_BRANCH" ]; then
+ # Check if pusher is the allowed user
+ if [ "$PUSHER" != "$ALLOWED_MASTER_USER" ]; then
+ echo "==============================================="
+ echo "ERROR: Only $ALLOWED_MASTER_USER can push to master branch."
+ echo "==============================================="
+ echo ""
+ echo "You are authenticated as: $PUSHER"
+ echo ""
+ echo "Please:"
+ echo " 1. Push to a feature branch instead:"
+ echo " git push origin your-feature-branch"
+ echo ""
+ echo " 2. Request a merge to master via email"
+ echo "==============================================="
+ exit 1
+ fi
+fi
+
+# Allow the push
+exit 0
+END_HOOK
+
+# ============================================================
+# HELPER FUNCTIONS
+# ============================================================
+
+# Sanitize and validate project name
+sub validate_project_name {
+ my ($name) = @_;
+
+ # Check if empty
+ if (!defined $name || $name eq '') {
+ return (0, "Project name cannot be empty");
+ }
+
+ # Check length
+ if (length($name) > 100) {
+ return (0, "Project name too long (max 100 characters)");
+ }
+
+ # Check for invalid characters
+ if ($name =~ m{[^a-zA-Z0-9_\-]}) {
+ return (0, "Project name can only contain letters, numbers, hyphens, and underscores");
+ }
+
+ # Check for path traversal attempts
+ if ($name =~ /\.\./ || $name =~ m{/} || $name =~ m{\\}) {
+ return (0, "Project name cannot contain '..' or path separators");
+ }
+
+ # Check for reserved git names
+ my @reserved = qw(.git hooks objects refs info config description HEAD);
+ if (grep { lc($name) eq lc($_) } @reserved) {
+ return (0, "Project name '$name' is reserved by git");
+ }
+
+ return (1, $name);
+}
+
+# Safely execute a command with proper escaping
+sub safe_system {
+ my @args = @_;
+ my $result = system(@args);
+ return ($result == 0);
+}
+
+# Safely create a file with content
+sub safe_write_file {
+ my ($filepath, $content) = @_;
+
+ open(my $fh, '>', $filepath) or return 0;
+ print $fh $content;
+ close($fh) or return 0;
+
+ return 1;
+}
+
+# Cleanup function for partial repository
+sub cleanup_repo {
+ my ($repo_dir) = @_;
+
+ if (-d $repo_dir) {
+ print "Cleaning up partial repository...\n";
+ remove_tree($repo_dir, {error => \my $err});
+ if (@$err) {
+ warn "Warning: Some cleanup errors occurred\n";
+ }
+ }
+}
+
+# ============================================================
+# MAIN SCRIPT
+# ============================================================
+
+print "===========================================\n";
+print " Git Repository Creation Wizard\n";
+print "===========================================\n\n";
+
+# Verify REPO_BASE exists and is accessible
+my $repo_base = REPO_BASE;
+if (!-d $repo_base) {
+ die "Error: Repository base directory '$repo_base' does not exist.\n";
+}
+if (!-w $repo_base) {
+ die "Error: Repository base directory '$repo_base' is not writable.\n";
+}
+
+# Get and validate project name
+my $project_name;
+my $repo_dir;
+while (1) {
+ print "Enter project name: ";
+ chomp($project_name = <STDIN>);
+
+ my ($valid, $result) = validate_project_name($project_name);
+ if (!$valid) {
+ print "Error: $result\n";
+ print "Please try again.\n\n";
+ next;
+ }
+
+ $project_name = $result;
+ $repo_dir = File::Spec->catdir($repo_base, $project_name);
+
+ # Check if repository already exists
+ if (-e $repo_dir) {
+ print "Error: Repository '$project_name' already exists.\n";
+ print "Please choose a different name.\n\n";
+ next;
+ }
+
+ last;
+}
+
+# Get project description
+print "Enter project description: ";
+chomp(my $description = <STDIN>);
+if ($description eq '') {
+ $description = "No description provided";
+}
+
+# Get category
+# these are categories that I use, you can setup your own...
+my %CATEGORIES = (
+ 1 => '1 - programs',
+ 2 => '2 - scripts',
+ 3 => '3 - other',
+);
+
+print "\nSelect category:\n";
+foreach my $key (sort keys %CATEGORIES) {
+ print " $key - $CATEGORIES{$key}\n";
+}
+
+my $category;
+while (1) {
+ print "Enter category number: ";
+ chomp(my $category_num = <STDIN>);
+
+ if (!exists $CATEGORIES{$category_num}) {
+ print "Error: Invalid category number. Please try again.\n\n";
+ next;
+ }
+
+ $category = $CATEGORIES{$category_num};
+
+ last;
+}
+
+# Get owner name
+print "Enter owner name: ";
+chomp(my $owner = <STDIN>);
+if ($owner eq '') {
+ $owner = "Unknown";
+}
+
+## NOTE: language and license are not standard git information I use
+## them on my git server, but you can comment them out or just skip
+## during the wizards run.
+
+# Get license
+print "Enter license (e.g., MIT, GPL, Apache): ";
+chomp(my $license = <STDIN>);
+if ($license eq '') {
+ $license = "Unknown";
+}
+
+# Get programming language
+print "Enter primary language (e.g., Perl, Python, JavaScript): ";
+chomp(my $language = <STDIN>);
+if ($language eq '') {
+ $language = "Unknown";
+}
+
+# Ask if repository is public (default: yes)
+print "Is the repository public? (Y/n): ";
+chomp(my $is_public = <STDIN>);
+$is_public = lc($is_public);
+
+# Default to public if empty or yes
+my $public = ($is_public eq '' || $is_public eq 'y' || $is_public eq 'yes') ? 1 : 0;
+
+# Confirm creation
+print "\n===========================================\n";
+print " Repository Summary\n";
+print "===========================================\n";
+print "Name: $project_name\n";
+print "Description: $description\n";
+print "Category: $category\n";
+print "Owner: $owner\n";
+print "License: $license\n";
+print "Language: $language\n";
+print "Visibility: " . ($public ? "Public" : "Private") . "\n";
+print "Path: $repo_dir\n";
+print "===========================================\n";
+print "Create this repository? (yes/no): ";
+chomp(my $confirm = <STDIN>);
+
+if (lc($confirm) ne 'yes' && lc($confirm) ne 'y') {
+ print "Repository creation cancelled.\n";
+ exit 0;
+}
+
+# Create repository with error handling
+print "\nCreating repository...\n";
+
+eval {
+ # Create directory
+ if (!safe_system('mkdir', '-p', $repo_dir)) {
+ die "Cannot create directory $repo_dir";
+ }
+
+ # Change to repository directory
+ chdir($repo_dir) or die "Cannot change to directory $repo_dir: $!";
+
+ # Initialize bare repository
+ if (!safe_system('git', 'init', '--bare', '--shared')) {
+ die "git init failed";
+ }
+
+ # Create description file
+ if (!safe_write_file('description', $description)) {
+ die "Cannot create description file";
+ }
+
+ # Create git-daemon-export-ok if public
+ if ($public) {
+ if (!safe_write_file('git-daemon-export-ok', '')) {
+ die "Cannot create git-daemon-export-ok";
+ }
+ }
+
+ # Create category file
+ if (!safe_write_file('category', $category)) {
+ die "Cannot create category file";
+ }
+
+ # Set owner using git config
+ if (!safe_system('git', 'config', '-f', 'config', 'gitweb.owner', $owner)) {
+ die "Failed to set owner";
+ }
+
+ # Set license using git config
+ if (!safe_system('git', 'config', '-f', 'config', 'gitweb.license', $license)) {
+ die "Failed to set license";
+ }
+
+ # Set language using git config
+ if (!safe_system('git', 'config', '-f', 'config', 'gitweb.language', $language)) {
+ die "Failed to set language";
+ }
+
+ # Add to safe directories
+ if (!safe_system('git', 'config', '--global', '--add', 'safe.directory', $repo_dir)) {
+ warn "Warning: Failed to add to safe directories\n";
+ }
+
+ # Create branch protection hook with username substitution
+ my $hook_content = $HOOK_UPDATE_TEMPLATE;
+ my $master_user = MASTER_BRANCH_USER;
+ $hook_content =~ s/__MASTER_USER__/$master_user/g;
+
+ if (!safe_write_file('hooks/update', $hook_content)) {
+ die "Cannot create update hook";
+ }
+
+ # Make hook executable
+ if (!chmod(0755, 'hooks/update')) {
+ die "Cannot make update hook executable";
+ }
+
+ # Create custom-summary directory
+ if (!make_path("custom-summary")) {
+ die "Cannot create custom-summary directory";
+ }
+
+ # Create README.html
+ if (!safe_write_file('README.html', $README_TEMPLATE)) {
+ die "Cannot create README.html";
+ }
+
+ # Create config.js
+ if (!safe_write_file('custom-summary/config.js', $CONFIG_JS_TEMPLATE)) {
+ die "Cannot create config.js";
+ }
+
+ # Set ownership to git user
+ if (!safe_system('chown', '-R', 'git:git', $repo_dir)) {
+ die "Failed to set ownership";
+ }
+
+ # Set permissions
+ if (!safe_system('chmod', '-R', '755', $repo_dir)) {
+ die "Failed to set permissions";
+ }
+};
+
+# Handle errors with cleanup
+if ($@) {
+ my $error = $@;
+ print "\n===========================================\n";
+ print " ERROR: Repository Creation Failed\n";
+ print "===========================================\n";
+ print "Error: $error\n";
+
+ cleanup_repo($repo_dir);
+
+ print "===========================================\n";
+ exit 1;
+}
+
+# Success message
+print "\n===========================================\n";
+print " Repository Created Successfully!\n";
+print "===========================================\n";
+print "Repository: $project_name\n";
+print "Location: $repo_dir\n";
+print "Visibility: " . ($public ? "Public" : "Private") . "\n";
+print "\nYou can now clone it with:\n";
+print " git clone git\@git.dpolakovic.space:$project_name\n";
+if ($public) {
+ print "\nOr publicly via:\n";
+ print " git clone git://git.dpolakovic.space/$project_name\n";
+}
+print "===========================================\n";
+
+exit 0;